﻿using UvsChess;
using System;

namespace StudentAI.BitBoard
{
    public sealed class Board
    {

        internal Piece[] Squares;

        internal bool InsufficientMaterial;

        internal int Score;

        internal ulong zobristHash;
        internal ulong hashB;      //Game state 1 move ago
        internal ulong hashBB;      //Game state 2 move ago
        internal ulong hashBBB;    //Game state 3 ago

        #region zobristValues
        private static ulong[] zobristValues = new ulong[769];
        #endregion

        //Game Over Flags
        internal bool BlackCheck;
        internal bool BlackMate;
        internal bool WhiteCheck;
        internal bool WhiteMate;
        internal bool StaleMate;
        internal byte RepeatedMove;
        internal bool EndGamePhase;
        internal MoveContent LastMove;
        internal ChessColor WhoseMove;
        AILoggerCallback logger;

        public void setLogger(AILoggerCallback log)
        {
            logger = log;
        }


        #region Constructors

        //Default Constructor

        public Board(ChessBoard uvsChessBoard, ChessColor turnColor)
        {
            Squares = new Piece[64];

            for (int x = 0; x < ChessBoard.NumberOfColumns; x++)
            {
                for (int y = 0; y < ChessBoard.NumberOfRows; y++)
                {
                    ChessPiece piece = uvsChessBoard[x, y];
                    if (piece != ChessPiece.Empty)
                    {
                        int idx = y * 8 + x;
                        Squares[idx] = new Piece(piece);
                    }
                }
            }
            WhoseMove = turnColor;

            zobristHash = calcZobrist();
            RepeatedMove = 0;
            hashB = 0;
            hashBB = 0;
            hashBBB = 0;

        }

        private Board(Piece[] squares)
        {
            Squares = new Piece[64];
            for (byte x = 0; x < 64; x++)
            {
                if (squares[x] != null)
                {
                    Squares[x] = new Piece(squares[x]);
                }
            }
        }

        //Constructor for a board that just has a score.  This is used so we can have min/max value boards quickly
        internal Board(int score)
        {
            Score = score;
        }

        //Copy Constructor
        public Board(Board board)
        {
            Squares = new Piece[64];

            for (byte x = 0; x < 64; x++)
            {
                if (board.Squares[x] != null)
                {
                    Squares[x] = new Piece(board.Squares[x]);
                }
            }
            EndGamePhase = board.EndGamePhase;

            RepeatedMove = board.RepeatedMove;
            BlackCheck = board.BlackCheck;
            WhiteCheck = board.WhiteCheck;
            StaleMate = board.StaleMate;
            WhiteMate = board.WhiteMate;
            BlackMate = board.BlackMate;
            WhoseMove = board.WhoseMove;

            zobristHash = board.zobristHash;
            hashB = board.hashB;
            hashBB = board.hashBB;
            hashBBB = board.hashBBB;

            Score = board.Score;
            LastMove = new MoveContent(board.LastMove);
        }

        #endregion

        public static void initZobrist()
        {
            var buffer = new byte[sizeof(Int64)];
            Random rnd = new Random(348764067);
            for (int i = 0; i < 769; i++)
            {
                rnd.NextBytes(buffer);
                zobristValues[i] = BitConverter.ToUInt64(buffer, 0);
            }
        }

        /// <summary>
        /// Calculate the zobrist hash based on the input board
        /// </summary>
        /// <param name="b"></param>
        /// <returns></returns>
        private ulong calcZobrist()
        {
            ulong zobrist = (this.WhoseMove == ChessColor.White) ? 0 : zobristValues[0]; //Color location
            for (int i = 0; i < 64; i++)
            {
                Piece p = Squares[i];
                if (p == null || p.PieceType == ChessPieceType.None)
                    continue;
                int pieceIdx = (p.PieceColor == ChessColor.White) ? 1 : 385;
                pieceIdx += ((int)p.PieceType) * 64 + i;
                //Console.WriteLine(p.PieceType +":"+(int)p.PieceType + "@" + i + "="+pieceIdx);
                zobrist ^= zobristValues[pieceIdx]; //Xor the piece unique number with the hash
            }
            return zobrist;
        }



        /// <summary>
        /// Update existing zobrist hash based on a move
        /// </summary>
        /// <param name="zobrist"></param>
        /// <param name="b"></param>
        /// <param name="from"></param>
        /// <param name="src"></param>
        /// <param name="target"></param>
        /// <param name="to"></param>
        /// <param name="dst"></param>
        /// <returns></returns>
        private ulong updateZobrist(ulong zobrist, ChessColor turnColor,
                ChessPieceType from, byte src, //Piece Moving
                ChessPieceType target, byte dst,//From state
                ChessPieceType afterType //Might be promoted
        )
        {
            //Change color
            zobrist ^= zobristValues[0];

            //Index based on color into the array
            int fromColorIdx = 1;
            int toColorIdx = 385;
            if (turnColor == ChessColor.Black)
            {
                fromColorIdx = 385;
                toColorIdx = 1;

            }
            //Remove from piece
            int srcIdx = fromColorIdx + ((int)from) * 64 + src;
            zobrist ^= zobristValues[srcIdx];

            //Remove target piece
            if (target != ChessPieceType.None)
            {
                //Console.WriteLine("Removing target");
                int targetIdx = toColorIdx + ((int)target) * 64 + dst;
                zobrist ^= zobristValues[targetIdx];
            }

            //Add the to piece
            //ChessPieceType toPiece = from; //If we promoted a pawn we need to update it
            //Console.WriteLine("Promoting to " + afterType);
            int destIdx = fromColorIdx + ((int)afterType) * 64 + dst;
            //Console.WriteLine("z idx to " + destIdx);
            zobrist ^= zobristValues[destIdx];
            return zobrist;
        }

        #region PrivateMethods

        private bool PromotePawns(Piece piece, byte dstPosition, ChessPieceType promoteToPiece)
        {
            if (piece.PieceType == ChessPieceType.Pawn)
            {
                if (dstPosition < 8)
                {
                    Squares[dstPosition].PieceType = promoteToPiece;
                    return true;
                }
                if (dstPosition > 55)
                {
                    Squares[dstPosition].PieceType = promoteToPiece;
                    return true;
                }
            }

            return false;
        }
        #endregion

        #region InternalMethods

        //Fast Copy
        internal Board FastCopy()
        {
            Board clonedBoard = new Board(Squares);
            clonedBoard.EndGamePhase = EndGamePhase;
            clonedBoard.WhoseMove = WhoseMove;

            clonedBoard.zobristHash = zobristHash;
            clonedBoard.hashB = hashB;
            clonedBoard.hashBB = hashBB;
            clonedBoard.hashBBB = hashBBB;
            clonedBoard.RepeatedMove = RepeatedMove;

            return clonedBoard;
        }

        public MoveContent MovePiece(byte srcPosition, byte dstPosition, ChessPieceType promoteToPiece)
        {
            Piece movePiece = Squares[srcPosition];

            //Save the piece type so the zobrist hash update can use it
            ChessPieceType savedSrcType = movePiece.PieceType;
            //Record my last move
            LastMove = new MoveContent();

            Piece capturePiece = Squares[dstPosition];
            if (capturePiece != null)
            {
                LastMove.TakenPiece = new PieceTaken(capturePiece.PieceColor, capturePiece.PieceType,
                                                            capturePiece.Moved, dstPosition);
            }
            else
            {
                LastMove.TakenPiece = new PieceTaken(ChessColor.White, ChessPieceType.None, false,
                                                            dstPosition);
            }


            LastMove.MovingPiece = new PieceMoving(movePiece.PieceColor, movePiece.PieceType, movePiece.Moved, srcPosition, dstPosition);

            //Delete the piece in its source position
            Squares[srcPosition] = null;

            //Add the piece to its new position
            movePiece.Moved = true;
            movePiece.Selected = false;
            Squares[dstPosition] = movePiece;

            //Switch whose turn it is
            WhoseMove = WhoseMove == ChessColor.White ? ChessColor.Black : ChessColor.White;

            //Promote Pawns 
            if (PromotePawns(movePiece, dstPosition, promoteToPiece))
            {
                LastMove.PawnPromoted = true;
            }
            else
            {
                LastMove.PawnPromoted = false;
            }

            ulong nextHash = updateZobrist(this.zobristHash, movePiece.PieceColor, //Before Game state
                                             savedSrcType, srcPosition,  //Piece to move
                                             LastMove.TakenPiece.PieceType, dstPosition, //Type of the piece taken
                                             movePiece.PieceType); //Promotion state
            //Keep track of repeat moves, if we hit three it is stalemate in our calcs

            if (this.WhoseMove == ChessColor.White)
            {
                hashBBB = hashBB;
                hashBB = hashB;
                hashB = zobristHash;
            }
            if (hashB == hashBBB || zobristHash == hashBB)
            {
                RepeatedMove++;
            }
            else
            {
                RepeatedMove = 0;
            }
            zobristHash = nextHash;

            return LastMove;
        }

        private static string GetColumnFromByte(byte column)
        {
            switch (column)
            {
                case 0:
                    return "a";
                case 1:
                    return "b";
                case 2:
                    return "c";
                case 3:
                    return "d";
                case 4:
                    return "e";
                case 5:
                    return "f";
                case 6:
                    return "g";
                case 7:
                    return "h";
                default:
                    return "a";
            }
        }

        public string Fen(bool boardOnly)
        {
            string output = "";
            byte blankSquares = 0;

            for (byte x = 0; x < 64; x++)
            {
                byte index = x;

                if (this.Squares[index] != null)
                {
                    if (blankSquares > 0)
                    {
                        output += blankSquares.ToString();
                        blankSquares = 0;
                    }

                    if (this.Squares[index].PieceColor == ChessColor.Black)
                    {
                        output += Piece.GetPieceTypeShort(this.Squares[index].PieceType).ToLower();
                    }
                    else
                    {
                        output += Piece.GetPieceTypeShort(this.Squares[index].PieceType);
                    }
                }
                else
                {
                    blankSquares++;
                }

                if (x % 8 == 7)
                {
                    if (blankSquares > 0)
                    {
                        output += blankSquares.ToString();
                        output += "/";
                        blankSquares = 0;
                    }
                    else
                    {
                        if (x > 0 && x != 63)
                        {
                            output += "/";
                        }
                    }
                }
            }

            if (this.WhoseMove == ChessColor.White)
            {
                output += " w ";
            }
            else
            {
                output += " b ";
            }

            string spacer = "";


            if (this.Squares[60] != null)
            {
                if (this.Squares[60].Moved == false)
                {
                    if (this.Squares[63] != null)
                    {
                        if (this.Squares[63].Moved == false)
                        {
                            output += "K";
                            spacer = " ";
                        }
                    }
                    if (this.Squares[56] != null)
                    {
                        if (this.Squares[56].Moved == false)
                        {
                            output += "Q";
                            spacer = " ";
                        }
                    }
                }
            }

            if (this.Squares[4] != null)
            {
                if (this.Squares[4].Moved == false)
                {
                    if (this.Squares[7] != null)
                    {
                        if (this.Squares[7].Moved == false)
                        {
                            output += "k";
                            spacer = " ";
                        }
                    }
                    if (this.Squares[0] != null)
                    {
                        if (this.Squares[0].Moved == false)
                        {
                            output += "q";
                            spacer = " ";
                        }
                    }
                }
            }

            if (output.EndsWith("/"))
            {
                output.TrimEnd('/');
            }
            else
            {
                output += spacer + "- ";
            }

            return output.Trim();
        }


        #endregion
    }
}
